import scipy
import random
import copy
from helper_codes.general_stimulus import general_stimulus, GeneralStim

def random_checks_settings():
    '''
    This function tells you what settings are needed and in what order for this stimulus.
    Inputs:
        None
    Returns:
        settings        : A list of tupples.  In each tupple is
                            (setting_name, setting_description, default_value).
                          The list is of length num_settings + 1, so that the final
                             value in the list is True/False wether or not the stimulus
                             can be triggered via the parallel port or not.
    '''
    settings = []
    settings.append( ('checks',
                     'Number of checks.  Will create a nxn grid of checks. (best if power of 2)',
                     8 ))
    settings.append( ('update_period',
                     'How many frames between screen updates.',
                     3 ))
    settings.append( ('shades',
                     'Number of different shades of grey. (2 for black/white only)',
                     8 ))
    settings.append( ('duration',
                     'How long the stimulus will last. (seconds)',
                     20.0 ))
    settings.append( ('win_size',
                     'The dimensions of the stimulus delivery window. (pixels) [list]',
                     [1440,900] ))
    settings.append( ('refresh_rate',
                     'The number of screen refreshes per second. (Hz)',
                     60 ))
    settings.append( ('random_seed',
                     'The number to seed the random number generator.',
                     40 ))
    settings.append(True) # this stimulus can be triggered
    return settings

def random_checks_runtime(checks, update_period, shades, duration, 
                         win_size, refresh_rate, random_seed): 
    """
    Calculate and return the number of seconds that this stimulus will need to run, after being triggered.
    """
    return duration + 0.99 # 0.99 added so rounding is always up.

class Stim(GeneralStim):
    """
    This is a basic stimulus object, it must have:
        .setup(window) method to set itself up to be drawn
        .draw() method to draw itself to the display
        .evolve() method to set itself up for the next draw
        .exhausted variable set to True after stim is done.
    """
    def __init__(self, checks, update_period, shades, duration, 
                  win_size, refresh_rate, random_seed, preview, save_frames):
        self.checks = checks
        self.update_period = update_period
        self.shades = shades
        self.duration = duration
        self.win_size = win_size
        self.refresh_rate = refresh_rate
        self.preview = preview
        self.save_frames = save_frames
        random.seed(random_seed)

        self.exhausted = False
        self.max_unique_frames = 500 
        self.times_evolved = 0
        self.times_since_update = 0

    def setup(self, window):
        from psychopy import visual
        # helper function for making textures
        def make_texture(shade_list, checks):
            texture = scipy.ones((checks,checks),dtype=scipy.float64)
            for i in xrange(checks):
                for j in xrange(checks):
                    texture[i][j] = random.choice(shade_list)
            return texture

        # ---------------------------
        # -- Generate the stimulus --
        # ---------------------------
        # determine the possible shade values in a list
        shade_list = scipy.linspace(-1.0, 1.0, self.shades)

        # determine how many unique frames need to be drawn.
        num_frames = int( self.duration * self.refresh_rate )

        # make the textures we'll use to draw
        num_textures = min( num_frames, self.max_unique_frames )
        texture_bank = [ make_texture(shade_list, self.checks) 
                         for i in xrange(num_textures) ]
        # create the patchstim we will draw
        self.check_stim = visual.PatchStim( window, 
                                        tex=texture_bank[0], 
                                        size=[1.0,1.0], 
                                        units='norm')
        self.texture_bank = texture_bank
        self.num_frames = num_frames
        self.textures = [self.check_stim]

    def draw(self):
        self.check_stim.draw()

    def evolve(self):
        self.times_evolved += 1
        self.times_since_update += 1
        if self.times_since_update >= self.update_period:
            try:
                self.check_stim.setTex(texture_bank[self.times_evolved])
            except:
                # exception was texture_bank index exceeded
                self.check_stim.setTex(random.choice(self.texture_bank))
            self.times_since_update = 0

        if self.times_evolved >= self.num_frames:
            if (not self.preview) or self.save_frames:
                self.exhausted = True
        
        

def random_checks(checks, update_period, shades, duration, 
                         win_size, refresh_rate, random_seed, 
                         window=None,
                         eye_cup=None, 
                         rf=None, 
                         save_frames=False,
                         save_frames_factor=1.0,
                         save_frames_size=100,
                         display_saved_frames=False,
                         preview=True):
    """
    This stimulus is a random check pattern updating in a
        square the size of the receptive field.
    Inputs:
        checks          : number of checks.  Will create a nxn grid of checks. (best if power of 2)
        update_period   : how many frames between screen updates.
        shade           : number of different shades of grey. (2 for black/white only)
        duration        : how long the stimulus will last. (seconds)
        win_size        : dimensions of the screen in pixels, (1440,900) is default
        refresh_rate    : refresh rate in Hz of the video, 60.0 is default.
        random_seed     : the seed for the random number generator.
        --- kwargs --
        window          : a psychopy.visual Window object, if not passed it will be constructed.
                        :    if passed it will not be closed (a blank screen will be drawn)
        eye_cup         : An Eye_cup object so we can draw the eye_cup on the screen and
                             set up the normalized coordinate system (size of eye_cup)
        rf              : an RF object so we can set up the normalized coordinate system
        frame_info      : if None, then no frames are saved, otherwise it is a 
                                dictionary with 
                                    'factor':float 
                                so we can scale down the frames and
                                output a frames.gif (an animated gif file)
                                and 
                                    'times':list
                                so we know how many frames to render.
        preview         : if True then the frames will display immediately,
                             otherwise we will wait for a pulse on the parallel port.
    Returns:
        times           : The times when frames were shown, with zero being the first frame time.
    """
    stim_list = [ Stim(checks, update_period, shades, duration, 
                       win_size, refresh_rate, random_seed, preview, save_frames) ]
    shade = 1.0
    return_values = general_stimulus( stim_list, shade, win_size,
                                         window=window,
                                         eye_cup=eye_cup, 
                                         rf=rf, 
                                         save_frames=save_frames,
                                         save_frames_factor=save_frames_factor,
                                         save_frames_size=save_frames_size,
                                         display_saved_frames=display_saved_frames,
                                         preview=preview)
    return return_values

if __name__ == '__main__':
    # ---------------------------------
    # -- Set the stimulus parameters --
    # ---------------------------------
    
    checks        = 8
    update_period = 60
    shades        = 2  
    duration      = 4

    # display parameters
    win_size = (1440,900)  # in pixels
    refresh_rate = 60.0    # in Hz
    random_seed = 40

    from helper_codes.RF import RF
    rf = RF.from_rf_file()

    return_values = random_checks(checks, update_period, shades, duration, 
                         win_size, refresh_rate, random_seed, 
                         eye_cup=None, 
                         rf=rf,
                         save_frames = True, 
                         save_frames_size = 500,
                         save_frames_factor = 5.0,
                         display_saved_frames = False,
                         preview=True)
        

